//go:build windows
// +build windows

package win进程类

import (
	"fmt"
	文本类 "gitee.com/go_888/extends/goframe/gstr"
	内部类2 "gitee.com/go_888/extends/windows/internal/windows"
	"gitee.com/go_888/internal-call/gopsutil/process"
	"golang.org/x/sys/windows"
	"log"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"unsafe"
)

// ID取路径 根据给定的进程ID获取进程的执行文件路径。
// 参数:
// 进程ID - 进程ID。
// 返回值:
// 进程的执行文件路径。
// 如果获取路径过程中发生错误，则返回空字符串和错误信息。
func ID取路径(进程ID int) string {
	// 尝试根据进程ID创建一个新的Process对象。
	p, err := process.NewProcess(int32(进程ID))
	if err != nil {
		// 如果创建Process对象失败，则返回错误信息。
		log.Printf("X取进程路径(),打开进程失败:" + err.Error())
		return ""
	}
	// 返回进程的执行文件路径。
	exe, _ := p.Exe()
	return exe
}

// X枚举 使用Windows API枚举当前系统中的进程信息。
// 返回一个 进程信息 切片，以及在枚举过程中可能发生的错误。
// 来源 https://github.com/mitchellh/go-ps
func X枚举() ([]*X进程信息, error) {
	// 定义Windows路径最大长度常量
	const (
		MAX_PATH = 260
	)

	// PROCESSENTRY32 是Windows API结构体，包含进程的信息。
	type PROCESSENTRY32 struct {
		Size              uint32
		CntUsage          uint32
		ProcessID         uint32
		DefaultHeapID     uintptr
		ModuleID          uint32
		CntThreads        uint32
		ParentProcessID   uint32
		PriorityClassBase int32
		Flags             uint32
		ExeFile           [MAX_PATH]uint16
	}

	// 调用CreateToolhelp32Snapshot API获取进程快照。
	handle, _, _ := procCreateToolhelp32Snapshot.X调用(
		0x00000002, // 打开进程和线程快照的标志
		0)          // 无特定进程ID，表示所有进程
	if handle < 0 {
		return nil, syscall.GetLastError() // 如果句柄无效，返回最后的错误信息
	}
	defer procCloseHandle.X调用(handle) // 确保函数返回前关闭句柄

	// 初始化PROCESSENTRY32结构体变量
	var entry PROCESSENTRY32
	entry.Size = uint32(unsafe.Sizeof(entry))
	ret, _, _ := procProcess32FirstW.X调用(handle, uintptr(unsafe.Pointer(&entry)))
	if ret == 0 {
		return nil, fmt.Errorf("检索进程信息时出错。")
	}

	// 创建一个结果切片，用于存储进程信息
	results := make([]*X进程信息, 0, 50)
	for {
		// 处理当前进程信息
		e := &entry
		// 查找字符串结束位置
		end := 0
		for {
			if e.ExeFile[end] == 0 {
				break
			}
			end++
		}

		// 将进程信息添加到结果切片中
		results = append(results, &X进程信息{
			X进程ID:  int(e.ProcessID),
			X父进程ID: int(e.ParentProcessID),
			X进程名称:  syscall.UTF16ToString(e.ExeFile[:end]), // 将UTF16字符串转换为Go字符串
		})

		// 检索下一个进程信息
		ret, _, _ := procProcess32NextW.X调用(handle, uintptr(unsafe.Pointer(&entry)))
		if ret == 0 {
			break
		}
	}

	return results, nil // 返回枚举到的进程信息和nil错误
}

// X取同名ID 通过给定的进程名称获取一系列进程的ID。
// 参数:
// 进程名 - 需要查找的进程名称。
// 区分大小写 - 可选参数，决定是否区分大小写进行进程名称匹配，默认为不区分大小写。
// 返回值:
// 一个包含匹配进程ID的整数数组。
func X取同名ID(进程名 string, 区分大小写 ...bool) []int {
	// 初始化区分大小写的标志，默认为false，即不区分大小写。
	区分大小写2 := false
	if len(区分大小写) > 0 {
		区分大小写2 = 区分大小写[0]
	}

	// 获取系统中所有进程的信息数组。
	进程数组, _ := X枚举()
	// 初始化用于存储匹配进程ID的数组。
	结果句柄数组 := []int{}
	for _, 进程 := range 进程数组 {
		// 根据是否区分大小写，选择不同的比较方式。
		if 区分大小写2 {
			// 如果区分大小写，直接比较进程名称。
			if 进程.X进程名称 == 进程名 {
				结果句柄数组 = append(结果句柄数组, 进程.X进程ID)
			}
		} else {
			// 如果不区分大小写，将进程名称转换为小写后比较。
			if 文本类.X到小写(进程.X进程名称) == 文本类.X到小写(进程名) {
				结果句柄数组 = append(结果句柄数组, 进程.X进程ID)
			}
		}
	}
	// 返回匹配的进程ID数组。
	return 结果句柄数组
}

// X创建 用于启动一个新的进程，并可选择等待其完成。
// 程序路径: 要执行的程序的路径。
// 参数: 传递给程序的命令行参数。
// 是否等待: 是否等待进程结束后再返回。
// 工作目录: 进程的工作目录。
// 隐藏窗口: 是否隐藏被启动的窗口。
// 返回值: 进程ID
func X创建(程序路径 string, 参数 []string, 是否等待 bool, 工作目录 string, 隐藏窗口 bool) (int, error) {
	// 构建完整的命令行参数
	cmd := exec.Command(程序路径, 参数...)

	// 设置工作目录
	cmd.Dir = 工作目录

	// 设置隐藏窗口属性
	if 隐藏窗口 {
		cmd.SysProcAttr = &syscall.SysProcAttr{
			HideWindow: true,
		}
	}

	// 启动进程
	if err := cmd.Start(); err != nil {
		return 0, err
	}

	// 如果需要等待进程结束
	if 是否等待 {
		if err := cmd.Wait(); err != nil {
			// 获取进程退出状态
			if exitErr, ok := err.(*exec.ExitError); ok {
				if _, ok := exitErr.Sys().(syscall.WaitStatus); ok {
					return int(cmd.Process.Pid), nil
				}
			}
			return 0, err
		}
	}

	// 返回进程ID
	return int(cmd.Process.Pid), nil
}

// X取自进程ID 返回当前进程的ID。
// 该函数调用了syscall包中的Getpid函数，以便获取当前进程的标识符。
// 返回值为当前进程的ID，类型为int。
func X取自进程ID() int {
	return syscall.Getpid()
}

// ID取进程名 通过进程ID获取进程名称。
// 参数 进程ID: 要查询的进程ID。
// 返回值: 该进程ID对应的进程名称，如果找不到则返回空字符串。
func ID取进程名(进程ID int) string {
	return 内部类2.ID取进程名(进程ID)
}

// ID查找进程 通过进程ID查找特定的进程信息。
// 参数:
// 进程ID - 需要查找的进程的ID。
// 返回值:
// *X进程信息 - 如果找到对应进程ID的进程，则返回进程信息指针；否则返回nil。
// error - 如果枚举进程失败，则返回错误；否则返回nil。
func ID查找进程(进程ID int) (*X进程信息, error) {
	// X枚举 用于获取系统中所有进程的信息列表。
	// 如果枚举进程失败，返回nil和错误；成功则继续处理。
	ps, err := X枚举()
	if err != nil {
		return nil, err
	}

	// 遍历所有进程，查找具有指定进程ID的进程信息。
	for _, p := range ps {
		if p.X进程ID == 进程ID {
			return p, nil
		}
	}
	// 如果没有找到指定进程ID的进程，返回nil和nil。
	return nil, nil
}

// ID是否有效 检查给定的进程ID是否存在于当前系统中。
// 参数:
// 进程ID - 需要检查的进程ID。
// 返回值:
// bool - 如果进程ID存在，返回true；否则返回false。
func ID是否有效(进程ID int) bool {
	exists, _ := process.PidExists(int32(进程ID))
	return exists
}

// ID取窗口句柄 通过给定的进程ID获取窗口句柄。
// 这个函数遍历所有顶级窗口，寻找与指定进程ID相匹配的窗口。
// 参数:
// 进程ID - 需要获取窗口句柄的进程ID。
// 返回值:
// 匹配的窗口的句柄，如果没有匹配的窗口则返回0。
func ID取窗口句柄(进程ID int) int {
	// 获取所有顶级窗口的信息
	窗口信息 := 内部类2.X枚举顶级窗口()

	// 遍历窗口信息，寻找进程ID匹配的窗口
	for _, v := range 窗口信息 {
		if 进程ID == v.X进程id {
			return v.X窗口句柄
		}
	}

	// 如果没有找到匹配的窗口，返回0
	return 0
}

// X取启动参数 返回当前进程的启动参数列表。
// 这些参数是在进程启动时传递给go运行时的命令行参数。
// 返回值是一个字符串切片，其中包含每个参数。
func X取启动参数() []string {
	return os.Args
}

// X取父ID 通过遍历当前系统中所有进程的方式，找出指定进程的父进程ID。
// 参数 进程ID: 需要查询其父进程ID的目标进程ID。
// 返回值: 如果找到目标进程，则返回其父进程ID；如果未找到，则返回0。
func X取父ID(进程ID int) int {
	// X进程_枚举函数用于获取当前系统中所有进程的信息。
	ps, _ := X枚举()
	// 遍历所有进程，寻找目标进程的父进程ID。
	for _, p := range ps {
		if p.X进程ID == 进程ID {
			return p.X父进程ID
		}
	}
	return 0
}

// X终止进程 通过指定的进程ID终止一个进程。
// 参数:
// 进程ID - 需要终止的进程的ID。
// 返回值:
// 成功终止进程返回true，否则返回false。
func X终止进程(进程ID int) bool {
	// 打开进程句柄
	hProcess, err := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, uint32(进程ID))
	if err != nil {
		log.Printf("无法打开进程 %d: %v\n", 进程ID, err)
		return false
	}
	// 确保在函数返回前关闭进程句柄
	defer syscall.CloseHandle(hProcess)

	// 终止进程
	err = syscall.TerminateProcess(hProcess, 1)
	if err != nil {
		log.Printf("无法终止进程 %d: %v\n", 进程ID, err)
		return false
	}
	// 成功终止进程
	return true
}

// X管理员权限运行 尝试以管理员权限重新启动当前或指定的进程。
// 这个函数主要用于在当前进程需要管理员权限但未以管理员权限运行时，重新启动自身或其他进程。
// 参数 进程路径 是一个可变参数，用于指定要以管理员权限运行的进程路径。如果未提供，则默认为当前进程。
// 返回值是一个错误，如果以管理员权限运行进程失败，则返回相应的错误。
func X管理员权限运行(进程路径 ...string) error {
	// "runas" 是用于以管理员身份运行进程的动词。
	verb := "runas"
	// 获取当前可执行文件的路径。
	exe, _ := os.Executable()
	if len(进程路径) > 0 {
		exe = 进程路径[0]
	}

	// 获取当前工作目录。
	cwd, _ := os.Getwd()
	// 将命令行参数转换为字符串形式。
	args := strings.Join(os.Args[1:], " ")

	// 将各种字符串转换为UTF16指针，以便ShellExecute函数使用。
	verbPtr, _ := syscall.UTF16PtrFromString(verb)
	exePtr, _ := syscall.UTF16PtrFromString(exe)
	cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
	argPtr, _ := syscall.UTF16PtrFromString(args)

	// SW_NORMAL 是一个标志，表示以什么方式显示窗口。
	var showCmd int32 = 0 //0, 隐藏powershell运行

	// 调用ShellExecute函数以管理员权限运行当前进程。
	err := windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd)
	// 检查ShellExecute函数是否执行成功。
	if err != nil {
		return err
	}
	return nil
}

// X是否为管理员权限 检查当前进程是否具有管理员权限。
// 该函数尝试打开物理磁盘来判断是否具有管理员权限。
// 返回值说明：
// - 如果返回true，表示当前进程具有管理员权限。
// - 如果返回false，表示当前进程不具有管理员权限。
func X是否为管理员权限() bool {
	// 尝试打开物理磁盘0来检查权限
	_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
	if err != nil {
		// 如果打开失败，说明没有管理员权限
		log.Println("管理员权限:无")
		return false
	}
	return true
}

// X限制进程多开 检查并限制具有相同窗口类名和标题的窗口数量不超过指定限制。
// 参数:
// 窗口类名: 需要限制的窗口的类名。
// 窗口标题: 需要限制的窗口的标题。
// 限制数量: 允许存在的最大窗口数量，如果为负则默认为1。
// 返回值:
// bool: 如果活动的窗口数量不超过限制数量，则返回true；否则返回false。
func X限制进程多开(窗口类名 string, 窗口标题 string, 限制数量 int) bool {
	// 调整限制数量，确保逻辑正确
	if 限制数量 >= 0 {
		限制数量 = 1
	}

	// 获取所有子窗口句柄数组
	返回数组 := 内部类2.X枚举子孙窗口(0)
	var 结果句柄数组 []int

	// 遍历所有子窗口，寻找匹配的窗口
	for _, 句柄 := range 返回数组 {
		// 清除窗口类名和标题的可能干扰字符
		窗口标题 = 文本类.X删首尾空与字符(窗口标题)
		窗口类名 = 文本类.X删首尾空与字符(窗口类名)
		当前类名 := 文本类.X删首尾空与字符(内部类2.X取类名(句柄))
		当前标题 := 文本类.X删首尾空与字符(内部类2.X取标题(句柄))

		// 如果当前窗口匹配给定的类名和标题，则添加到结果数组
		if 当前类名 == 窗口类名 && 当前标题 == 窗口标题 {
			结果句柄数组 = append(结果句柄数组, 句柄)
		}
	}

	// 检查活动窗口数量是否在限制范围内
	return len(结果句柄数组) <= 限制数量
}

/*
程序_删除自身
程序_是否被调试
程序_是否关闭
程序_锁定到任务栏

输入法中英切换 https://bbs.125.la/forum.php?mod=viewthread&tid=14741502
*/
